/**
 *    Copyright (C) 2008  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

#ifndef __BITMAP_TEST_H__
#define __BITMAP_TEST_H__

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/portability/Stream.h>

#include <cubefs/core/tools/bitmap.hpp>

using namespace std;

namespace cubefs {
namespace core {
namespace tests {
namespace bitmap {

template <typename T>
class Buffer {
    T *buffer;
public:
    typedef T element;

Buffer(char *b): buffer((T*)b) {}

    T &operator[](T idx) const {
        return buffer[idx];
    }
};

template<class Type>
class Test: public CPPUNIT_NS::TestFixture {
    CPPUNIT_TEST_SUITE(Test);

    CPPUNIT_TEST(testGetBits);
    CPPUNIT_TEST(testPopCntFW);
    CPPUNIT_TEST(testPopCntBW);
    CPPUNIT_TEST(testZeroCntFW);
    CPPUNIT_TEST(testZeroCntBW);
    CPPUNIT_TEST(testCopy);
    CPPUNIT_TEST(testShift);

    CPPUNIT_TEST_SUITE_END();

protected:
    char buffer[4096]; // 4K
public:

    Test() {
    }

    int countTestCases() const {
        return 1;
    }


    void setUp() {
        for (int c = 0; c < (int)sizeof(buffer); c++) {
            buffer[c] = 0;
        }
    }

    void tearDown() {

    }

    void testGetBits() {
        Buffer<Type> buf(buffer);

        for (int c = 0; c < 8; c++) {
            buffer[c] = 0xAA;
        }

        for (Type c = 0; c < 255; c++) {
            set_bits(buf, c, c, (Type)8);
            CPPUNIT_ASSERT_EQUAL(c, get_bits(buf, c, (Type)8));
        }
    }

    void testPopCntFW() {
        Buffer<Type> buf(buffer);

        for (int c = 0; c < 9; c++) {
            buffer[c] = 0xFF;
        }
        buffer[9] = 0x7;

        Type bits = 75;
        for (Type c = 0; c < bits; c++) {
            Type cnt = count_one_fw(buf, c, bits);
            CPPUNIT_ASSERT_EQUAL(bits - c, cnt);
        }

        for (Type c = 0; c < bits; c++) {
            Type cnt = count_one_fw(buf, c, bits + 8);
            CPPUNIT_ASSERT_EQUAL(bits - c, cnt);
        }

        for (Type c = 75; c < 128; c++) {
            Type cnt = count_one_fw(buf, c, (Type)128);
            CPPUNIT_ASSERT_EQUAL((Type)0, cnt);
        }
    }


    void testPopCntBW() {
        Buffer<Type> buf(buffer);

        for (int c = 0; c < 9; c++) {
            buffer[c] = 0xFF;
        }
        buffer[9] = 0x7;

        Type bits = -1;
        for (Type c = 74; c > bits; c--) {
            Type cnt = count_one_bw(buf, c, bits);
            CPPUNIT_ASSERT_EQUAL(c + 1, cnt);
        }

        for (Type c = 74; c > bits; c--) {
            Type cnt = count_one_bw(buf, c, bits - 9);
            CPPUNIT_ASSERT_EQUAL(c + 1, cnt);
        }

        for (Type c = 127; c > 74; c--) {
            Type cnt = count_one_bw(buf, c, (Type)-1);
            CPPUNIT_ASSERT_EQUAL((Type)0, cnt);
        }
    }

    void testZeroCntFW() {
        Buffer<Type> buf(buffer);

        for (int c = 0; c < 16; c++) {
            buffer[c] = 0xFF;
        }

        for (int c = 0; c < 9; c++) {
            buffer[c] = 0x0;
        }
        buffer[9] = 0xF8;

        Type bits = 75;
        for (Type c = 0; c < bits; c++) {
            Type cnt = count_zero_fw(buf, c, bits);
            CPPUNIT_ASSERT_EQUAL(bits - c, cnt);
        }

        for (Type c = 0; c < bits; c++) {
            Type cnt = count_zero_fw(buf, c, bits + 8);
            CPPUNIT_ASSERT_EQUAL(bits - c, cnt);
        }

        for (Type c = 75; c < 128; c++) {
            Type cnt = count_zero_fw(buf, c, (Type)128);
            CPPUNIT_ASSERT_EQUAL((Type)0, cnt);
        }
    }

    void testZeroCntBW() {
        Buffer<Type> buf(buffer);

        for (int c = 0; c < 24; c++) {
            buffer[c] = 0xFF;
        }

        for (int c = 0; c < 9; c++) {
            buffer[c] = 0x0;
        }
        buffer[9] = 0xF8;

        Type bits = -1;
        for (Type c = 74; c > bits; c--) {
            Type cnt = count_zero_bw(buf, c, bits);
            CPPUNIT_ASSERT_EQUAL(c + 1, cnt);
        }

        buffer[0] = 0xFF;

        for (Type c = 74; c > 7; c--) {
            Type cnt = count_zero_bw(buf, c, bits);
            CPPUNIT_ASSERT_EQUAL(c + 1 - 8, cnt);
        }

        for (Type c = 127; c > 74; c--) {
            Type cnt = count_zero_bw(buf, c, (Type)-1);
            CPPUNIT_ASSERT_EQUAL((Type)0, cnt);
        }

        for (Type c = 7; c > -1; c--) {
            Type cnt = count_zero_bw(buf, c, (Type)-1);
            CPPUNIT_ASSERT_EQUAL((Type)0, cnt);
        }
    }

    void testCopy() {
        char target_buf[4096];

        Buffer<Type> buf(buffer);
        Buffer<Type> target(target_buf);

        Type value = 0x8ABDC173;

        Type bitsize = type_bitsize<Type>();

        set_bits(buf, (Type)8, value, bitsize);
        set_bits(buf, (Type)8 + bitsize, value, bitsize);
        set_bits(buf, (Type)8 + 2*bitsize, value, 16);

        for (Type c = 0; c < bitsize; c++) {
            copy_bits(buf, target, (Type)8, c, bitsize);
            Type tgt_value = get_bits(target, c, bitsize);
            CPPUNIT_ASSERT_EQUAL(value, tgt_value);
        }

        for (Type c = 0; c < bitsize; c++) {
            copy_bits(buf, target, (Type)8, c, bitsize * 2 + 16);

            Type tgt_value0 = get_bits(target, c, bitsize);
            Type tgt_value1 = get_bits(target, c + bitsize, bitsize);
            Type tgt_value2 = get_bits(target, c + bitsize * 2, 16);

            CPPUNIT_ASSERT_EQUAL(value, tgt_value0);
            CPPUNIT_ASSERT_EQUAL(value, tgt_value1);
            CPPUNIT_ASSERT_EQUAL(value & 0xFFFF, tgt_value2);
        }
    }


    void testShift() {
        Buffer<Type> buf(buffer);

        Type value = 0x8ABDC173;
        Type bitsize = type_bitsize<Type>();

        for (Type c = 0; c < 10; c++) {
            set_bits(buf, c * bitsize, value, bitsize);
            Type tgt_value = get_bits(buf, c * bitsize, bitsize);
            CPPUNIT_ASSERT_EQUAL(value, tgt_value);
        }

        set_bits(buf, 10 * bitsize, value, 16);

        for (Type c = 0; c < bitsize; c++) {
            shift_bits(buf, c, c + 1, bitsize * 10 + 16);
            for (Type d = 0; d < 10; d++) {
                Type tgt_value = get_bits(buf, c + d * bitsize + 1, bitsize);
                CPPUNIT_ASSERT_EQUAL(value, tgt_value);
            }

            Type tgt_value = get_bits(buf, c + 10 * bitsize + 1, 16);
            CPPUNIT_ASSERT_EQUAL(value & 0xFFFF, tgt_value);
        }
    }
};

} //bitmap
} //tests
} //core
} //cubefs

#endif
